'''
Description: 
Author: 天涯过客
Date: 2025-05-13 15:57:39
'''
'''
Description: 编译原理图形界面
Author: 天涯过客
Date: 2025-05-13 15:57:39
'''
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QTextEdit, QVBoxLayout,
    QHBoxLayout, QPushButton, QLabel, QSplitter, QPlainTextEdit,QMenu
    ,QFileDialog
)
from PyQt5.QtCore import Qt, QRect, QSize
from PyQt5.QtGui import QPainter, QColor, QTextFormat, QFont
import sys
import os

#获取当前路径上一级
current_path = os.path.abspath(os.path.join(os.getcwd(), os.pardir))


from lexer import run_lexer,Token
from parser import run_parser
from middleCode import run_generate_code
from optimize import run_code_optimization
from Semantic import run_semantic_analysis
from object import run_object_code
from dosbox import run_asm_in_dosbox


# 带行号编辑器组件
class LineNumberArea(QWidget):
    def __init__(self, editor):
        super().__init__(editor)
        self.code_editor = editor

    def sizeHint(self):
        return QSize(self.code_editor.lineNumberAreaWidth(), 0)

    def paintEvent(self, event):
        self.code_editor.lineNumberAreaPaintEvent(event)

class CodeEditor(QPlainTextEdit):
    def __init__(self):
        super().__init__()
        self.setFont(QFont("Courier", 11))
        self.lineNumberArea = LineNumberArea(self)

        self.blockCountChanged.connect(self.updateLineNumberAreaWidth)
        self.updateRequest.connect(self.updateLineNumberArea)
        self.cursorPositionChanged.connect(self.highlightCurrentLine)

        self.updateLineNumberAreaWidth(0)
        self.highlightCurrentLine()

    def lineNumberAreaWidth(self):
        digits = len(str(max(1, self.blockCount())))
        return 3 + self.fontMetrics().horizontalAdvance('9') * digits

    def updateLineNumberAreaWidth(self, _):
        self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0)

    def updateLineNumberArea(self, rect, dy):
        if dy:
            self.lineNumberArea.scroll(0, dy)
        else:
            self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())

        if rect.contains(self.viewport().rect()):
            self.updateLineNumberAreaWidth(0)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        cr = self.contentsRect()
        self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height()))

    def lineNumberAreaPaintEvent(self, event):
        painter = QPainter(self.lineNumberArea)
        painter.fillRect(event.rect(), QColor(240, 240, 240))

        block = self.firstVisibleBlock()
        block_number = block.blockNumber()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        bottom = top + int(self.blockBoundingRect(block).height())

        while block.isValid() and top <= event.rect().bottom():
            if block.isVisible() and bottom >= event.rect().top():
                number = str(block_number + 1)
                painter.setPen(Qt.gray)
                painter.drawText(0, top, self.lineNumberArea.width() - 5,
                                 self.fontMetrics().height(), Qt.AlignRight, number)
            block = block.next()
            top = bottom
            bottom = top + int(self.blockBoundingRect(block).height())
            block_number += 1

    def highlightCurrentLine(self):
        if not self.isReadOnly():
            selection = QTextEdit.ExtraSelection()
            line_color = QColor(230, 240, 255)
            selection.format.setBackground(line_color)
            selection.format.setProperty(QTextFormat.FullWidthSelection, True)
            selection.cursor = self.textCursor()
            selection.cursor.clearSelection()
            self.setExtraSelections([selection])

# 主窗口
class CompilerWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("编译器 v1.0")
        self.resize(1200, 800)

        self.cur_code = "" # 当前代码
        # 产生结果
        self.tokens = []
        self.ast = None # 语法树
        self.symbol_table = None # 符号表
        self.errors = [] # 语义错误列表
        self.quads = None # 四元式列表
        self.object_code = None # 目标代码
        # 创建中央部件和总布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        # ===== 主工作区 =====
        work_area_splitter = QSplitter(Qt.Horizontal)

        # 左侧：代码编辑器（带行号）
        self.editor = CodeEditor()
        self.editor.setPlaceholderText("请输入代码...")
        work_area_splitter.addWidget(self.editor)


                # ===== 顶部按钮区域 =====
        top_button_bar = QHBoxLayout()
        self.file_open_button = QPushButton("打开文件")
        self.file_open_button.clicked.connect(self.open_file)
        # TODO 完成打开文件，可选择文件后展示在代码输入框里面
        self.lexer_button = QPushButton("词法分析")
        #这里点击词法分析按钮 会下拉有两个子按钮为手动实现和自动实现
        self.lexer_menu = QMenu(self)
        lexer_manual_action = self.lexer_menu.addAction("手动实现")

        lexer_auto_action = self.lexer_menu.addAction("自动实现")
        self.lexer_button.setMenu(self.lexer_menu)

        # 连接信号槽
        lexer_manual_action.triggered.connect(lambda: self.action_lexer_manual())
        lexer_auto_action.triggered.connect(lambda: self.on_button_clicked("词法分析 - 自动实现"))
        
        self.syntax_button = QPushButton("语法分析")
        self.semantic_button = QPushButton("语义分析")
        self.ir_button = QPushButton("中间代码生成")
        self.optimize_button = QPushButton("代码优化")
        self.code_button = QPushButton("目标代码生成")


        # 普通按钮绑定槽函数
        self.syntax_button.clicked.connect(lambda: self.action_parser())
        self.semantic_button.clicked.connect(lambda: self.action_semantic_analysis())
        self.ir_button.clicked.connect(lambda: self.action_generate_code())
        self.code_button.clicked.connect(lambda: self.action_object_code())
        self.optimize_button.clicked.connect(lambda: self.action_optimize())
        self.compile_button = QPushButton("编译")
        self.compile_button.clicked.connect(lambda: self.action_compile())
        self.run_button = QPushButton("运行")
        self.run_button.clicked.connect(lambda: self.action_run())
        self.compile_run_button = QPushButton("编译运行")
        self.compile_run_button.clicked.connect(lambda: self.action_compile_and_run())

        top_button_bar.addWidget(self.file_open_button)
        top_button_bar.addWidget(self.lexer_button)
        top_button_bar.addWidget(self.syntax_button)
        top_button_bar.addWidget(self.semantic_button)
        top_button_bar.addWidget(self.ir_button)
        top_button_bar.addWidget(self.optimize_button)
        top_button_bar.addWidget(self.code_button)
        top_button_bar.addWidget(self.compile_button)
        top_button_bar.addWidget(self.run_button)
        top_button_bar.addWidget(self.compile_run_button)

        top_button_bar.addStretch()
        main_layout.addLayout(top_button_bar)

        # 右侧：输出区 + 日志
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)
        right_layout.setContentsMargins(0, 0, 0, 0)

        self.output = QTextEdit()
        self.output.setReadOnly(True)
        self.output.setPlaceholderText("输出内容...")

        self.log_label = QTextEdit()
        self.log_label.setReadOnly(True)
        self.log_label.setPlaceholderText("日志信息...")

        right_layout.addWidget(self.output)
        right_layout.addWidget(self.log_label)

        work_area_splitter.addWidget(right_widget)
        work_area_splitter.setStretchFactor(0, 2)
        work_area_splitter.setStretchFactor(1, 1)

        main_layout.addWidget(work_area_splitter)
    
    def on_button_clicked(self, msg):
        self.output.clear()
        self.output.append(f"【操作】：{msg}")

    def open_file(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "选择代码文件", "", "Text Files (*.txt *.c *.cpp *.py);;All Files (*)")
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                    self.editor.setPlainText(content)
                    self.output.append(f"【打开文件】：{file_path}")
            except Exception as e:
                self.output.append(f"【错误】：无法读取文件 - {e}")
    
    # 词法分析手动
    def action_lexer_manual(self):
        self.cur_code = self.editor.toPlainText()
        self.tokens,errors = run_lexer(code=self.cur_code)
        self.output.clear()
        self.output.append("【词法分析结果】")
        self.output.append("类型\t值\t行\t列")
        tokens_output = "\n".join(f"{token.type}\t{token.value}\t{token.line_num}\t{token.col_num}" for token in self.tokens)
        self.output.append(tokens_output)
        if errors:
            self.log_label.clear()
            self.log_label.append("【错误信息】")
            error_output = "\n".join(f"{error}" for error in errors)
            self.log_label.append(error_output)
        else:
            self.log_label.append("没有发现错误！")
    
    def action_parser(self):
        if not self.tokens or self.cur_code != self.editor.toPlainText():
            self.action_lexer_manual()
        syntax_tree = run_parser(self.tokens)
        self.ast = syntax_tree
        self.output.clear()
        self.output.append("【语法分析结果】")
        
        self.output.append(syntax_tree.__repr__())
    
    def action_semantic_analysis(self):
        if self.ast is None or self.cur_code != self.editor.toPlainText():
            self.action_parser()
        # 这里调用语义分析的函数
        self.symbol_table,self.errors = run_semantic_analysis(self.ast)
        self.output.clear()
        self.output.append("【语义分析结果】")
        self.output.append(self.symbol_table.__repr__())
        if self.errors:
            self.log_label.clear()
            self.log_label.append("【错误信息】")
            error_output = "\n".join(f"{error}" for error in self.errors)
            self.log_label.append(error_output)
        else:
            self.log_label.append("没有发现错误！")

    def action_generate_code(self):
        if self.ast is None or self.cur_code != self.editor.toPlainText():
            self.action_parser()
        if self.errors:
            self.output.append("语义分析有误！请先修正错误！")
            return
        self.quads = run_generate_code(self.ast,self.symbol_table)
        self.output.clear()
        self.output.append("【中间代码生成结果】")
        for i, q in enumerate(self.quads):
            self.output.append(f"{i}: {q}")
    
    def action_optimize(self):
        # 如果还没生成四元式或代码变了，先重新生成
        if self.quads is None or self.cur_code != self.editor.toPlainText():
            self.action_generate_code()
        if self.errors:
            self.output.append("语义分析有误！请先修正错误！")
            return

        # 调用优化接口，得到两份列表
        flat_opt, bb_opt = run_code_optimization(self.quads)

        # 清空输出区
        self.output.clear()
        self.output.append("【代码优化结果】")

        # 逐条打印带基本块标记的那份列表
        # 每遇到 BB_START，就换行并标记基本块序号
        i = 0
        block_idx = 0
        for q in bb_opt:
            if q.op == 'BB_START':
                block_idx += 1
                self.output.append(f"\n-- 基本块 {block_idx} 开始 --")
            self.output.append(f"{i}: {q}")
            i += 1

        # 更新 quads 为平展优化后的那份
        self.quads = flat_opt

        # 日志提示
        self.log_label.clear()
        self.log_label.append("代码优化成功！")


    def action_object_code(self):
        if self.quads is None or self.cur_code != self.editor.toPlainText():
            self.action_generate_code()
        # 这里调用目标代码生成的函数
        self.object_code = run_object_code(self.quads)
        print(self.object_code)

        code_path = current_path+"\\asm\\test.asm"
        with open(code_path,'w') as f:
            # 保存汇编代码到文件
            f.write(self.object_code)
            f.close()
        self.output.clear()
        self.output.append("【目标代码生成结果】")
        self.output.append(self.object_code)
        self.log_label.append("目标代码生成成功！")
    
    def action_compile(self):
        if self.object_code is None or self.cur_code != self.editor.toPlainText():
            self.action_object_code()
        if self.errors:
            self.output.append("语义分析有误！请先修正错误！")
            return
        # 这里调用编译的函数
        run_asm_in_dosbox(1)
        self.output.clear()
        # 读取编译结果
        self.log_label.clear()
        output_path = current_path+"\\asm\\BUILD.LOG"
        with open(output_path, 'r', encoding='utf-8') as f:
            content = f.readlines()
            for i in range(0, len(content)):
                self.log_label.append(content[i])

    def action_run(self):
        if self.object_code is None or self.cur_code != self.editor.toPlainText():
            self.action_object_code()
        if self.errors:
            self.output.append("语义分析有误！请先修正错误！")
            return
        # 这里调用运行的函数
        run_asm_in_dosbox(2)
        self.output.clear()
        self.output.append("【运行结果】")
        # 读取运行结果
        output_path = current_path+"\\asm\\output.txt"
        with open(output_path, 'r', encoding='utf-8') as f:
            content = f.read()
            self.output.append(content)

    def action_compile_and_run(self):
        if self.object_code is None or self.cur_code != self.editor.toPlainText():
            self.action_object_code()
        if self.errors:
            self.output.append("语义分析有误！请先修正错误！")
            return
        # 这里调用编译和运行的函数
        run_asm_in_dosbox(3)
        self.output.clear()
        self.output.append("【编译运行结果】")
        # 读取运行结果
        output_path = current_path+"\\asm\\output.txt"
        with open(output_path, 'r', encoding='utf-8') as f:
            content = f.read()
            self.output.append(content)
        self.log_label.clear()
        ans_path = current_path+"\\asm\\BUILD.LOG"
        with open(ans_path, 'r', encoding='utf-8') as f:
            content = f.read()
            self.log_label.append(content)
        

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = CompilerWindow()
    window.show()
    sys.exit(app.exec_())